package ui;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.*;
import javax.swing.RowSorter.SortKey;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;

import core.Preferences;
import core.SetIndex;
import core.SetRecord;
import core.SongIndex;
import core.SongRecord;
import core.Utilities;

/*
 * Presenter.java
 *
 * Created on August 5, 2007, 8:46 PM
 */

/**
 * This is the main frame for the application. It attempts to load
 * saved songs, sets and preferences files; if they don't exist, they are created.
 * A lock file is created and freed by this class so that only one instance
 * may exist at a time.
 * @author  john
 */
@SuppressWarnings("serial")
public class Presenter extends JFrame {
	
	// Set System L&F before doing anything else
	static {
        try {
			UIManager.setLookAndFeel(
			    UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (UnsupportedLookAndFeelException e) {
			e.printStackTrace();
		}
	}
    
	public static final int VERSION = 3;
	public static final String VERSION_STRING = "Build 3",
								TITLE_STRING = "Envision";
	
	private static final File lockFile = new File(".lock");
	private FileOutputStream lock;
	
	private SongIndex songIndex;
	private SetIndex setIndex;
	private Preferences prefs;
	private JTable songTable, setTable;
	
	/**
	 * Creates a new Presenter window.
	 * (Only one window should exist at a time, though, because all instances
	 * write to the same data files.)
	 */
    public Presenter() {
    	super(TITLE_STRING);
    	
    	// allow only one instance
    	if (!acquireLockFile())
    		throw new Error("Couldn't create lock file; exiting.");
    	
    	if (!Preferences.BASE_FOLDER.exists())
    		if (!Preferences.BASE_FOLDER.mkdir())
    			throw new Error("Couldn't create \"data/\" folder; exiting.");
    	
    	if ((prefs = Preferences.load(Preferences.DEFAULT_FILE)) == null)
    		prefs = new Preferences();
    	
    	if ((songIndex = SongIndex.load(Preferences.SONG_INDEX_FILE)) == null)
    		songIndex = new SongIndex();
    	songIndex.setDisplayCols(2);
    	
    	if ((setIndex = SetIndex.load(Preferences.SET_INDEX_FILE)) == null)
    		setIndex = new SetIndex();
    	
        initComponents();
        initListeners();
        initLeftTabs();
        
        addWindowListener(new WindowAdapter() {
        	public void windowClosing(WindowEvent e) {
            	releaseLockFile();
        	}
        });
        
        setExtendedState(JFrame.MAXIMIZED_BOTH);
        setVisible(true);
    }
    
    private boolean acquireLockFile() {
    	try {
			// look for a lock file; if it's found, try to delete it 
			if (!lockFile.exists() || lockFile.delete()) {
				// hold onto the opened lock file, so other instances can't delete it
				lock = new FileOutputStream(lockFile);
				return true;
			}
			// if it does exist and it can't be deleted, then another
			//  instance is holding onto it
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
    }
    
    private void releaseLockFile() {
    	try {
    		if (lock != null) {
    			lock.close();
    			lockFile.delete();
    		}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
    }
    
    private void initComponents() {
        genToolbar = new JToolBar();
        newSongBtn = new JButton();
        newSetBtn = new JButton();
        saveBtn = new JButton();
        presentBtn = new JButton();
        leftTabPane = new JTabbedPane();
        
        mainTabPane = new CloseableTabPane();
        
        menuBar = new JMenuBar();
        fileMenu = new JMenu();
        newSongOptn = new JMenuItem();
        newSetOptn = new JMenuItem();
        jSeparator1 = new JSeparator();
//        openOptn = new JMenuItem();
        addOptn = new JMenuItem();
        importWAOptn = new JMenuItem();
        jSeparator2 = new JSeparator();
        exitOptn = new JMenuItem();
        editMenu = new JMenu();
        helpMenu = new JMenu();
        aboutOptn = new JMenuItem();
        prefsOptn = new JMenuItem();
        saveOptn = new JMenuItem();
        presentOptn = new JMenuItem();
        closeTabOptn = new JMenuItem();
        
        /* For later releases:
        cutBtn = new JButton();
        copyBtn = new JButton();
        pasteBtn = new JButton();
        undoBtn = new JButton();
        redoBtn = new JButton();
        undoOptn = new JMenuItem();
        redoOptn = new JMenuItem();
        */

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setIconImage(new ImageIcon(Presenter.class.getResource("/res/sm/audio-x-generic.png")).getImage());
        genToolbar.setFloatable(false);
        newSongBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/document-new.png")));
        newSongBtn.setToolTipText("New Song");
        newSongBtn.setFocusable(false);
        genToolbar.add(newSongBtn);

        newSetBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/folder-new.png")));
        newSetBtn.setToolTipText("New Set");
        newSetBtn.setFocusable(false);
        genToolbar.add(newSetBtn);

        saveBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/media-floppy.png")));
        saveBtn.setToolTipText("Save");
        saveBtn.setFocusable(false);
        genToolbar.add(saveBtn);

        genToolbar.addSeparator();

/*        cutBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/edit-cut.png")));
        cutBtn.setToolTipText("Cut");
        cutBtn.setFocusable(false);
        genToolbar.add(cutBtn);

        copyBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/edit-copy.png")));
        copyBtn.setToolTipText("Copy");
        copyBtn.setFocusable(false);
        genToolbar.add(copyBtn);

        pasteBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/edit-paste.png")));
        pasteBtn.setToolTipText("Paste");
        pasteBtn.setFocusable(false);
        genToolbar.add(pasteBtn);

        undoBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/edit-undo.png")));
        undoBtn.setToolTipText("Undo");
        undoBtn.setFocusable(false);
        genToolbar.add(undoBtn);

        redoBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/edit-redo.png")));
        redoBtn.setToolTipText("Redo");
        redoBtn.setFocusable(false);
        genToolbar.add(redoBtn);
        
        genToolbar.addSeparator();*/

        presentBtn.setIcon(new ImageIcon(Presenter.class.getResource("/res/med/video-display.png")));
        presentBtn.setToolTipText("Start Presentation");
        presentBtn.setFocusable(false);
        genToolbar.add(presentBtn);

//        leftTabPane.addTab("tab1", new JPanel());
//        mainTabPane.addTab("tab1", new JPanel());
        
        splitPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, leftTabPane, mainTabPane);

        fileMenu.setText("File");
        newSongOptn.setText("New Song");
        newSongOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/document-new.png")));
        newSongOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
        fileMenu.add(newSongOptn);

        newSetOptn.setText("New Set");
        newSetOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/folder-new.png")));
        newSetOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK));
        fileMenu.add(newSetOptn);
        
        saveOptn.setText("Save");
        saveOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/media-floppy.png")));
        saveOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
        fileMenu.add(saveOptn);
        
        closeTabOptn.setText("Close Tab");
        closeTabOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK));
        fileMenu.add(closeTabOptn);
        
        fileMenu.add(new JSeparator());
        
        presentOptn.setText("Present");
        presentOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/video-display.png")));
        presentOptn.setAccelerator(KeyStroke.getKeyStroke("F5"));
        fileMenu.add(presentOptn);

        fileMenu.add(jSeparator1);

//        openOptn.setText("Open Data Folder");
//        openOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/document-open.png")));
//        fileMenu.add(openOptn);

        addOptn.setText("Import Data Folder");
        addOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/document-save.png")));
        fileMenu.add(addOptn);

        importWAOptn.setText("Import from WA");
        importWAOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/go-down.png")));
        fileMenu.add(importWAOptn);

        fileMenu.add(jSeparator2);

        exitOptn.setText("Exit");
        exitOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/emblem-unreadable.png")));
        fileMenu.add(exitOptn);

        menuBar.add(fileMenu);

        editMenu.setText("Edit");

/*        undoOptn.setText("Undo");
        undoOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/edit-undo.png")));
        editMenu.add(undoOptn);

        redoOptn.setText("Redo");
        redoOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/edit-redo.png")));
        editMenu.add(redoOptn);
        
        editMenu.add(new JSeparator());
*/
        cutOptn = new JMenuItem(new DefaultEditorKit.CutAction());
        cutOptn.setText("Cut");
        cutOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/edit-cut.png")));
        cutOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
        editMenu.add(cutOptn);

        copyOptn = new JMenuItem(new DefaultEditorKit.CopyAction());
        copyOptn.setText("Copy");
        copyOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/edit-copy.png")));
        copyOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
        editMenu.add(copyOptn);

        pasteOptn = new JMenuItem(new DefaultEditorKit.PasteAction());
        pasteOptn.setText("Paste");
        pasteOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/edit-paste.png")));
        pasteOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
        editMenu.add(pasteOptn);
        
        editMenu.add(new JSeparator());
        
        prefsOptn.setText("Preferences");
        prefsOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/preferences-system.png")));
        prefsOptn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK));
        editMenu.add(prefsOptn);

        menuBar.add(editMenu);

        helpMenu.setText("Help");
        aboutOptn.setText("About");
        aboutOptn.setIcon(new ImageIcon(Presenter.class.getResource("/res/sm/help-browser.png")));
        helpMenu.add(aboutOptn);

        menuBar.add(helpMenu);

        setJMenuBar(menuBar);

        GroupLayout layout = new GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addComponent(genToolbar, GroupLayout.DEFAULT_SIZE, 800, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(splitPane, GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE)
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(genToolbar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(splitPane, GroupLayout.DEFAULT_SIZE, 531, Short.MAX_VALUE)
                .addContainerGap())
        );
        pack();
    }
    
    private void initListeners() {
    	
    	// init file choosers for the import menu options
    	final JFileChooser importFC = new JFileChooser("data/");	// TODO remove "data/" when testing done
    	final JFileChooser addFC = new JFileChooser("data/");
    	addFC.setDialogTitle("Import Folder");
		addFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		importFC.setDialogTitle("Import WA Database");
		importFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    	
    	ActionListener al = new ActionListener() {
    		public void actionPerformed(ActionEvent e) {
            	Object src = e.getSource();
            	
            	if (src == saveBtn || src == saveOptn) {
            		Component c = mainTabPane.getSelectedComponent();
            		if (c != null && c instanceof ActionListener) {
            			((ActionListener)c).actionPerformed(e);		// redispatch the event
            			
            			int i = mainTabPane.getSelectedIndex();		// update the tab title
                		mainTabPane.setTitleAt(i, c.getName());
            		}
            		
            		songIndex.save();
            		setIndex.save();
            	}
            	else if (src == newSongBtn || src == newSongOptn) {
            		mainTabPane.add(new SongEditor(songIndex));		// new blank song editor
            		mainTabPane.setSelectedIndex(mainTabPane.getTabCount()-1);	// set new tab as selected
            	}
            	else if (src == newSetBtn || src == newSetOptn) {
            		mainTabPane.add(new SetEditor(setIndex, songIndex, Presenter.this));	// new blank set editor
            		mainTabPane.setSelectedIndex(mainTabPane.getTabCount()-1);	// set new tab as selected
            	}
            	else if (src == presentBtn || src == presentOptn) {
            		startPresentation();
            	}
            	else if (src == exitOptn) {
            		// TODO check for open presentation, prompt for file save
            		
            		releaseLockFile();
            		System.exit(0);
            	}
            	else if (src == addOptn) {
            		int rv = addFC.showOpenDialog(Presenter.this);
            		if (rv == JFileChooser.APPROVE_OPTION)
            			songIndex.importFolder(addFC.getSelectedFile());
            	}
            	else if (src == importWAOptn) {
            		int rv = importFC.showOpenDialog(Presenter.this);
            		if (rv == JFileChooser.APPROVE_OPTION)
            			songIndex.importWADatabase(importFC.getSelectedFile());
            	}
            	else if (src == prefsOptn) {
            		openPreferencesPanel();
            	}
            	else if (src == closeTabOptn) {
            		closeCurrentTab();
            	}
            	else if (src == aboutOptn)
        			JOptionPane.showMessageDialog(Presenter.this, TITLE_STRING + "\n" + VERSION_STRING, "About", 
        					JOptionPane.INFORMATION_MESSAGE);
        	}
    	};
    	exitOptn.addActionListener(al);
    	aboutOptn.addActionListener(al);
    	saveBtn.setActionCommand("save");
    	saveBtn.addActionListener(al);
    	saveOptn.setActionCommand("save");
    	saveOptn.addActionListener(al);
    	newSongBtn.addActionListener(al);
    	newSongOptn.addActionListener(al);
    	newSetBtn.addActionListener(al);
    	newSetOptn.addActionListener(al);
    	presentBtn.addActionListener(al);
    	addOptn.addActionListener(al);
    	importWAOptn.addActionListener(al);
    	prefsOptn.addActionListener(al);
    	presentOptn.addActionListener(al);
    	closeTabOptn.addActionListener(al);
    }
    
    protected void closeCurrentTab() {
    	Component c = mainTabPane.getSelectedComponent();
		if (c != null) {
			if (c instanceof ActionListener)
				((ActionListener)c).actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "close"));
			
			mainTabPane.remove(c);
		}
    }
    
    protected void startPresentation() {
    	Component c = mainTabPane.getSelectedComponent();
		if (c != null) {
			if (c instanceof SongEditor) {
    			SetRecord set = new SetRecord(-1);
    			set.add(((SongEditor)c).record);
    			mainTabPane.add("Presentation", new PresentationPanel(set, songIndex, prefs));
    		}
			else if (c instanceof SetEditor)
				mainTabPane.add("Presentation", new PresentationPanel(((SetEditor)c).record, songIndex, prefs));
			
			mainTabPane.setSelectedIndex(mainTabPane.getTabCount()-1);	// set new tab as selected
		}
    }
    
    protected void openPreferencesPanel() {
    	String[] options = {"Save", "Cancel"};
    	PreferencesPanel pp = new PreferencesPanel(prefs);
    	int rc = JOptionPane.showOptionDialog(this,
    			pp,
    			"Preferences",
    			JOptionPane.YES_NO_OPTION,
    			JOptionPane.PLAIN_MESSAGE,
    			null,
    			options,
    			null);
    	if (rc == JOptionPane.YES_OPTION)
    		pp.save();
    }
    
    protected void openSetEditorTab(int index) {
    	SetRecord sr = setIndex.get(index);
    	boolean found = false;
    	
    	for (int i=0; i<mainTabPane.getTabCount() && !found; i++) {
    		Component c = mainTabPane.getComponentAt(i);
    		if (c instanceof SetEditor && ((SetEditor)c).record == sr) {
    			mainTabPane.setSelectedIndex(i);
    			found = true;
    		}
    	}
    	if (!found) {
    		mainTabPane.add(new SetEditor(sr, songIndex, this));
    		mainTabPane.setSelectedIndex(mainTabPane.getTabCount()-1);
    	}
    }
    
    protected void openSongEditorTab(int index) {
    	openSongEditorTab(songIndex.getByIndex(index));
    }
    
    protected void openSongEditorTab(SongRecord sr) {
    	boolean found = false;
    	
    	// check if song is open already
    	for (int i=0; i<mainTabPane.getTabCount() && !found; i++) {
    		Component c = mainTabPane.getComponentAt(i);
    		if (c instanceof SongEditor && ((SongEditor)c).record == sr) {
    			mainTabPane.setSelectedIndex(i);
    			found = true;
    		}
    	}
    	if (!found) {
    		mainTabPane.add(new SongEditor(sr));
    		mainTabPane.setSelectedIndex(mainTabPane.getTabCount()-1);	// set new tab as active
    	}
    }
    
    private int selectedRow(JTable table) {
    	return table.convertRowIndexToModel(table.getSelectedRow());
    }
    
    /**
     * Updates the selection of a table by searching through
     * the currently sorted column for items matching the search text.
     * @author john
     *
     */
    class TableSearchBox extends JTextField 
    		implements DocumentListener, KeyListener, FocusListener {
    	
    	private JTable table;
    	private int lastPos = -1;
    	private int lastKey = KeyEvent.VK_UNDEFINED;
    	
    	public TableSearchBox() {
			this(null);
		}
    	public TableSearchBox(JTable table) {
    		this.table = table;
			getDocument().addDocumentListener(this);
			addKeyListener(this);
			addFocusListener(this);
		}
    	
    	public void setTable(JTable table) {
			this.table = table;
		}
    	
    	/**
    	 * Searches the currently sorted column in the table for the text box's
    	 * text, and selects the first match if one is found.
    	 * If there is no sorted column in the table, it uses the first column.
    	 * @param initial	index to search from
    	 * @param down		direction of search: true - down, false - up
    	 * @param strict	true - term must start with 
    	 */
    	private void search(int initial, boolean down, boolean strict) {
    		if (table.getColumnCount() > 0) {
	    		List<? extends SortKey> keys = songTable.getRowSorter().getSortKeys();
	    		int j = ((keys.isEmpty()) ? 0 : keys.get(0).getColumn());
	    		final int rowCount = table.getRowCount();
	    		
	    		try {
		    		Pattern p = Pattern.compile((strict ? "" : ".*\\b") + getText() + ".*",
		    				Pattern.CASE_INSENSITIVE);
	//	    		System.out.println("searching from " + initial + ", " + (down? "down" : "up"));
	//	    		System.out.println("pattern is " + p);
		    		
		    		for (int i= initial;
		    					(down ? i<rowCount : i>=0);
		    					i += (down ? 1 : -1)) {
		    			Object val = table.getModel().getValueAt(table.convertRowIndexToModel(i), j);
		    			if (val != null && p.matcher(val.toString()).matches()) {
	//	    				System.out.println("matches row " + i);
		    				table.setRowSelectionInterval(i, i);
		    				table.scrollRectToVisible(table.getCellRect(i, j, true));
		    				lastPos = i;
		    				setBackground(java.awt.Color.white);
		    				return;
		    			}
		    		}
	    		} catch (PatternSyntaxException ex) {}
	    		
	    		setBackground(java.awt.Color.yellow);
    		}
    	}
    	
    	private void nextMatch(boolean down) {
   			search(lastPos + (down ? 1 : -1), down, false);
		}
    	
    	// DocumentListener
    	public void changedUpdate(DocumentEvent e) {}
		public void insertUpdate(DocumentEvent e) {
			search(0, true, true);
		};
		public void removeUpdate(DocumentEvent e) {
			if (getText().isEmpty()
					&& (lastKey == KeyEvent.VK_DELETE || lastKey == KeyEvent.VK_BACK_SPACE))
				table.requestFocus();
			else
				search(0, true, true);
		};
		
		// KeyListener
		public void keyPressed(KeyEvent e) {
			switch (lastKey = e.getKeyCode()) {
			case KeyEvent.VK_UP:
				nextMatch(false);
				break;
				
			case KeyEvent.VK_DOWN:
				nextMatch(true);
				break;
				
			case KeyEvent.VK_ENTER:
				table.requestFocus();
				if (!getText().isEmpty())
					table.dispatchEvent(e);
				break;
			}
		}
		public void keyReleased(KeyEvent e) {}
		public void keyTyped(KeyEvent e) {}
		
		// FocusListener
		public void focusGained(FocusEvent e) {
			setBackground(java.awt.Color.white);
		}
		public void focusLost(FocusEvent e) {
			setVisible(false);
			Presenter.this.repaint();
		}
    }
    
    private void initLeftTabs() {
    	TableSearchBox songSearchBox = new TableSearchBox();
    	
    	songTable = makeSongTable(songIndex, songSearchBox);
    	songSearchBox.setTable(songTable);
    	setTable = makeSetTable(setIndex);
    	
		JScrollPane tableView = new JScrollPane(songTable);
		Dimension d = tableView.getPreferredSize();
		d.width = 340;//songTable.getColumnModel().getTotalColumnWidth() + tableView.getVerticalScrollBar().getWidth();
		tableView.setPreferredSize(d);
		JPanel tablePanel = new JPanel();
    	tablePanel.setLayout(new GridBagLayout());
    	GridBagConstraints c = new GridBagConstraints();
    	c.gridy = 0;
    	c.fill = GridBagConstraints.BOTH;
    	c.weighty = 1;
    	tablePanel.add(tableView, c);
    	c.gridy++;
    	c.weighty = 0;
    	tablePanel.add(songSearchBox, c);
    	songSearchBox.setVisible(false);
		
		JScrollPane setTableView = new JScrollPane(setTable);
		d = setTableView.getPreferredSize();
		d.width = 340;
		setTableView.setPreferredSize(d);
		
		leftTabPane.add("Songs", tablePanel);
		leftTabPane.add("Sets", setTableView);
    }
    
    private JTable makeSetTable(final SetIndex setIndex) {
    	final JTable setTable = new JTable(setIndex);
    	setTable.setAutoCreateRowSorter(true);
    	
    	setTable.addKeyListener(new KeyAdapter() {
    		@Override
    		public void keyPressed(KeyEvent e) {
    			
    			switch (e.getKeyCode()) {
    			case KeyEvent.VK_ENTER:
    				if (setTable.getSelectedRow() != -1)
    					openSetEditorTab(selectedRow(setTable));
    				e.consume();
    				break;
    				
    			case KeyEvent.VK_DELETE: {
    				if (setTable.getSelectedRow() != -1) {
	    				String vartext = (setTable.getSelectedRowCount() > 1) ? "these sets" : "this set";
						int rc = JOptionPane.showConfirmDialog(
								Presenter.this,
								"Are you sure you want to delete " + vartext + "?",
								"Delete Set",
								JOptionPane.YES_NO_OPTION);
						
						if (rc == JOptionPane.YES_OPTION) {
							int[] selRows = Utilities.getSortedSelectionRows(setTable);
							
							for (int i=selRows.length-1; i>=0; i--)
								setIndex.remove(selRows[i]);
						}
    				}
					break;
    			}
    			
    			case KeyEvent.VK_END:
				case KeyEvent.VK_HOME:
					if (!e.isControlDown()) {
						songTable.dispatchEvent(new KeyEvent((Component)e.getSource(), e.getID(), e.getWhen(), KeyEvent.CTRL_DOWN_MASK, e.getKeyCode(), e.getKeyChar()));
						e.consume();
					}
					break;
    			
				default:
					// TODO: search box ?
					
    			}
    		}
    	});
    	setTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					openSetEditorTab(selectedRow(setTable));
				}
			}
    	});
    	
    	return setTable;
    }
    
    private JTable makeSongTable(final SongIndex songIndex, final JTextComponent searchBox) {
    	final JTable songTable = new JTable(songIndex);
		songTable.setAutoCreateRowSorter(true);
    	
		songTable.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				switch (e.getKeyCode()) {
				case KeyEvent.VK_ENTER:
					if (songTable.getSelectedRow() != -1) {
						// ctrl-enter:  add to set
						if (e.isControlDown()) {
							Component c = mainTabPane.getSelectedComponent();
		            		if (c != null && c instanceof SetEditor)
		            			for (int i : songTable.getSelectedRows())
		            				((SetEditor)c).addItem(songIndex.getByIndex(songTable.convertRowIndexToModel(i)));
						}
						// enter:  open for editing
	            		else
	            			for (int i : songTable.getSelectedRows())
	    						openSongEditorTab(songTable.convertRowIndexToModel(i));
					}
					e.consume();
					break;
				
				case KeyEvent.VK_DELETE:
					if (songTable.getSelectedRow() != -1) {
						String vartext = (songTable.getSelectedRowCount() > 1) ? "these songs" : "this song";
						int rc = JOptionPane.showConfirmDialog(
								Presenter.this,
								"Are you sure you want to delete " + vartext + "?",
								"Delete Song",
								JOptionPane.YES_NO_OPTION);
						
						if (rc == JOptionPane.YES_OPTION) {
							int[] selRows = Utilities.getSortedSelectionRows(songTable);
							
							for (int i=selRows.length-1; i>=0; i--)
								songIndex.removeByIndex(selRows[i]);
						}
					}
					break;
					
				case KeyEvent.VK_END:
				case KeyEvent.VK_HOME:
					if (!e.isControlDown()) {
						songTable.dispatchEvent(new KeyEvent((Component)e.getSource(), e.getID(), e.getWhen(), KeyEvent.CTRL_DOWN_MASK, e.getKeyCode(), e.getKeyChar()));
						e.consume();
					}
					break;
				}
			}
			
			@Override
			public void keyTyped(KeyEvent e) {
				// launch search box if a letter or the '.' key was typed
				char c = e.getKeyChar();
				if (Character.isLetterOrDigit(c) ||
						c == '.') {
					searchBox.setVisible(true);
					repaint();
					searchBox.requestFocus();
					if (c == '.')
						searchBox.setText(null);
					else
						searchBox.setText(Character.toString(c));
				}
			}
		});
		
		songTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					// ctrl-double click:  add to set
					if (e.isControlDown()) {
						Component c = mainTabPane.getSelectedComponent();
	            		if (c != null && c instanceof SetEditor)
	            			((SetEditor)c).addItem(songIndex.getByIndex(selectedRow(songTable)));
					}
					// double click:  open for editing
            		else
            			openSongEditorTab(selectedRow(songTable));
				}
			}
		});
		
		songTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		songTable.setFillsViewportHeight(true);
		//songTable.getColumnModel().getColumn(0).setPreferredWidth(tableView.getViewport().getViewSize().width);
		for (int i = 0; i < songTable.getColumnModel().getColumnCount(); i++) {
		    songTable.getColumnModel().getColumn(i).setPreferredWidth(160);
		}
		songTable.getRowSorter().toggleSortOrder(SongRecord.TITLE);
		
		/**
		 * This transfer handler supports the export of Lists of songs
		 * (SongSelections).
		 * @author john
		 *
		 */
		class SongTransferHandler extends TransferHandler {
	    	
	    	protected Transferable createTransferable(JComponent c) {
	    		JTable table = (JTable)c;
	    		int[] modelRows = Utilities.mapSelectionRowsToModel(table);
	    		List<SongRecord> selection = new ArrayList<SongRecord>(modelRows.length);
	    		for (int i : modelRows)
	    			selection.add(songIndex.getByIndex(i));
	    		
	    		return new SongSelection(selection);
	    	}
	    	
	    	@Override
	    	public int getSourceActions(JComponent c) {
	    		return COPY;
	    	}
	    }
		songTable.setDragEnabled(true);
		songTable.setTransferHandler(new SongTransferHandler());
		
		return songTable;
    }
    
    // Variables declaration - do not modify
    /* For later releases:
    private JButton copyBtn;
    private JButton cutBtn;
    private JButton pasteBtn;
    private JButton redoBtn;
    private JMenuItem redoOptn;
    private JButton undoBtn;
    private JMenuItem undoOptn;
    */
    private JMenuItem saveOptn;
    private JMenuItem pasteOptn;
    private JMenuItem copyOptn;
    private JMenuItem cutOptn;
    private JMenuItem aboutOptn;
    private JMenu editMenu;
    private JMenuItem exitOptn;
    private JMenuItem addOptn;
    private JMenu fileMenu;
    private JToolBar genToolbar;
    private JMenu helpMenu;
    private JMenuItem importWAOptn;
    private JSeparator jSeparator1;
    private JSeparator jSeparator2;
    private JTabbedPane leftTabPane;
    private JTabbedPane mainTabPane;
    private JMenuBar menuBar;
    private JButton newSetBtn;
    private JMenuItem newSetOptn;
    private JButton newSongBtn;
    private JMenuItem newSongOptn;
//    private JMenuItem openOptn;
    private JButton saveBtn;
    private JMenuItem prefsOptn;
    private JButton presentBtn;
    private JSplitPane splitPane;
    private JMenuItem presentOptn;
    private JMenuItem closeTabOptn;
    // End of variables declaration                   
    
}
